#include <cmath>
#include "Exp.h"

using namespace internal;

class ColumnExp: public Exp {
public:
	ColumnExp(const Output &in, int nr) {
		output = in[nr];
	}

	void doit(int) { }
};

/******* typeTraitsy rzutujace class na Type  **********/

template<class T>
struct toType {
	static Type X;
};

template<>
struct toType<int32_t> {
	static Type X;
};
Type toType<int32_t>::X = INT;

template<>
struct toType<double> {
	static Type X;
};
Type toType<double>::X = DOUBLE;

template<>
struct toType<bool> {
	static Type X;
};
Type toType<bool>::X = BOOL;

/******************************************************/

template<class T>
class ConstantExp: public Exp {
public:

	ConstantExp(T x) {
		T* data = new T[TABLESIZE];
		for (int i = 0; i < TABLESIZE; i++) data[i] = x;
		output = Output_elem((void *) data, toType<T>::X);
	}

	void doit(int) { }
};

// result_type to typ wynikowy dodawania typu A i typu B,

template<class A, class B>
struct traits {
	typedef double result_type;
};

template<>
struct traits<int32_t, int32_t> {
	typedef int32_t result_type;
};

// MAKRO tworzace szablon klasy wyrazen dwuargumentowych
#define TWO_ARGUMENTS_EXPRESSION(NAME, ACTION, RESULT, SPECIAL)	\
template<class A, class B>										 \
class NAME: public Exp {										   \
  private:														 \
  	RESULT *data ;												 \
  	A* sourceA ;												   \
  	B* sourceB ;												   \
  	Exp* a ;													   \
  	Exp* b ;													   \
  public:														  \
  	NAME(Exp* a_, Exp* b_) : a(a_), b(b_) {						\
  		data = new RESULT[TABLESIZE] ;							 \
  		output = Output_elem((void *) data, toType< RESULT >::X) ; \
  		sourceA = (A*) a->getOutputElem().column ;				 \
  		sourceB = (B*) b->getOutputElem().column ;				 \
  	}															  \
  	void doit(int size) {										  \
  		a->doit(size) ;											\
  		b->doit(size) ;											\
  		for(int i=0 ; i<size ; i++)								\
  			data[i] = (SPECIAL sourceA[i] ACTION sourceB[i]) ;	 \
  	}															  \
} ;


#define COMMA ,

// tworzenie szablonow

TWO_ARGUMENTS_EXPRESSION(AddExp, +, typename traits<A COMMA B>::result_type,)
TWO_ARGUMENTS_EXPRESSION(SubtractExp, -, typename traits<A COMMA B>::result_type,)
TWO_ARGUMENTS_EXPRESSION(MultiplyExp, *, typename traits<A COMMA B>::result_type,)
TWO_ARGUMENTS_EXPRESSION(LowerExp, <, bool,)
TWO_ARGUMENTS_EXPRESSION(GreaterExp, >, bool,)
TWO_ARGUMENTS_EXPRESSION(EqualExp, ==, bool,)
TWO_ARGUMENTS_EXPRESSION(NotEqualExp, !=, bool,)
TWO_ARGUMENTS_EXPRESSION(OrExp, ||, bool,)
TWO_ARGUMENTS_EXPRESSION(AndExp, &&, bool,)
TWO_ARGUMENTS_EXPRESSION(DivideExp, /, double, (double))

// makra instancjonujace szablony
#define TYPE_CHOICE_1(NAME, ACTION)							\
if(t1 == INT	&& t2 == INT)	{							 \
	/*printf("INT %s INT\n", #ACTION) ;*/					  \
	return new NAME<int32_t,int32_t> (a,b) ;					   \
}															  \
if(t1 == INT	&& t2 == DOUBLE) {							 \
	/*printf("INT %s DOUBLE\n", #ACTION) ; */				  \
	return new NAME<int32_t,double>(a,b) ;					   \
}															  \
if(t1 == DOUBLE && t2 == INT)   {							  \
	/*printf("DOUBLE %s INT\n", #ACTION) ;*/				   \
	return new NAME<double,int32_t> (a,b) ;					  \
}															  \
if(t1 == DOUBLE && t2 == DOUBLE) {							 \
	/*printf("DOUBLE %s DOUBLE\n", #ACTION) ;*/				\
	return new NAME<double,double>(a,b) ;					  \
}

#define TYPE_CHOICE_2(NAME, ACTION)							\
if(t1 == INT	&& t2 == INT)	{							 \
	/*printf("INT %s INT\n", #ACTION) ; */					 \
	return new NAME<int32_t,int32_t> (a,b) ;					   \
}															  \
if(t1 == DOUBLE	&& t2 == DOUBLE) {						  \
	/*printf("DOUBLE %s DOUBLE\n", #ACTION) ; */			   \
	return new NAME<double,double>(a,b) ;					  \
}															  \
if(t1 == BOOL && t2 == BOOL)   {							   \
	/*printf("BOOL %s BOOL\n", #ACTION) ; */				   \
	return new NAME<bool,bool> (a,b) ;						 \
}

#define TYPE_CHOICE_3(NAME, ACTION)							\
if(t1 == BOOL && t2 == BOOL)   {							   \
	/*printf("BOOL %s BOOL\n", #ACTION) ; */				   \
	return new NAME<bool,bool> (a,b) ;						 \
}

// MAKRO tworzace szablon klasy wyrazen jednoargumentowych
#define ONE_ARGUMENT_EXPRESSION(NAME, ACTION, RESULT)			  \
template<class A>												  \
class NAME: public Exp {										   \
  private:														 \
  	RESULT *data ;												 \
  	A* sourceA ;												   \
  	Exp* a ;													   \
  public:														  \
  	NAME(Exp* a_) : a(a_) {										\
  		data = new RESULT[TABLESIZE] ;							 \
  		output = Output_elem((void *) data, toType< RESULT >::X) ; \
  		sourceA = (A*) a->getOutputElem().column ;				 \
  	}															  \
  	void doit(int size) {										  \
  		a->doit(size) ;											\
  		for(int i=0 ; i<size ; i++)								\
  			data[i] = ACTION(sourceA[i]) ;						 \
  	}															  \
} ;

ONE_ARGUMENT_EXPRESSION(NotExp, !, bool)
ONE_ARGUMENT_EXPRESSION(NegateExp, -, A)
ONE_ARGUMENT_EXPRESSION(LogExp, log, double)

#define TYPE_CHOICE_4(NAME, ACTION)							\
if(t1 == BOOL)   {											 \
	/*printf("%s BOOL\n", #ACTION) ;*/						 \
	return new NAME<bool> (a) ;								\
}

#define TYPE_CHOICE_5(NAME, ACTION)							\
if(t1 == INT)   {											  \
	/*printf("%s INT\n", #ACTION) ;*/						  \
	return new NAME<int32_t> (a) ;							 \
}															  \
if(t1 == DOUBLE)   {										   \
	/*printf("%s DOUBLE\n", #ACTION) ;*/					   \
	return new NAME<double> (a) ;							  \
}

#define TYPE_CHOICE_6(NAME, ACTION)							\
if(t1 == INT)   {											  \
	/*printf("%s INT\n", #ACTION) ;*/						  \
	return new NAME<int32_t> (a) ;							 \
}															  \
if(t1 == DOUBLE)   {										   \
	/*printf("%s DOUBLE\n", #ACTION) ;*/					   \
	return new NAME<double> (a) ;							  \
}


template<class A>
class IfExpr: public Exp {
private:
	A *data;
	bool *source1;
	A *source2, *source3;
	Exp *a, *b, *c;

public:
	IfExpr(Exp* a_, Exp* b_, Exp* c_): a(a_), b(b_), c(c_) {
		data = new A[TABLESIZE];
		output = Output_elem((void *) data, toType< A >::X);
		source1 = (bool*) a->getOutputElem().column;
		source2 = (A*) b->getOutputElem().column;
		source3 = (A*) c->getOutputElem().column;
	}

	void doit(int size) {
		a->doit(size);
		b->doit(size);
		c->doit(size);
		for (int i = 0; i < size; i++)
			data[i] = (source1[i]) ? source2[i] : source3[i];
	}
};

Exp* createExp(const Expression &expression, const Output &source) {
	switch (expression.operator_()) {
	case Expression_Operator_COLUMN:
		return new ColumnExp(source, expression.column_id());

	case Expression_Operator_CONSTANT:
		if (expression.has_constant_int32()) {
			return new ConstantExp<int32_t > (expression.constant_int32());
		} else if (expression.has_constant_double()) {
			return new ConstantExp<double>(expression.constant_double());
		} else if (expression.has_constant_bool()) {
			return new ConstantExp<bool>(expression.constant_bool());
		} else assert(false);

	case Expression_Operator_ADD:
	case Expression_Operator_SUBTRACT:
	case Expression_Operator_MULTIPLY:
	case Expression_Operator_LOWER:
	case Expression_Operator_GREATER:
	case Expression_Operator_EQUAL:
	case Expression_Operator_NOT_EQUAL:
	case Expression_Operator_OR:
	case Expression_Operator_AND:
	case Expression_Operator_FLOATING_DIVIDE:
	{
		Exp* a = createExp(expression.children(0), source);
		Exp* b = createExp(expression.children(1), source);
		Type t1 = a->getOutputElem().type;
		Type t2 = b->getOutputElem().type;
		switch (expression.operator_()) {
		case Expression_Operator_ADD: TYPE_CHOICE_1(AddExp, +);
		case Expression_Operator_SUBTRACT: TYPE_CHOICE_1(SubtractExp, -);
		case Expression_Operator_MULTIPLY: TYPE_CHOICE_1(MultiplyExp, *);
			assert(false);
		case Expression_Operator_LOWER: TYPE_CHOICE_2(LowerExp, <);
		case Expression_Operator_GREATER: TYPE_CHOICE_2(GreaterExp, >);
		case Expression_Operator_EQUAL: TYPE_CHOICE_2(EqualExp, ==);
		case Expression_Operator_NOT_EQUAL: TYPE_CHOICE_2(NotEqualExp, !=);
			assert(false);
		case Expression_Operator_OR: TYPE_CHOICE_3(OrExp, ||);
		case Expression_Operator_AND: TYPE_CHOICE_3(AndExp, &&);
			assert(false);
		case Expression_Operator_FLOATING_DIVIDE: TYPE_CHOICE_1(DivideExp, /);
			assert(false);
		default: assert(false);
		}
		assert(false);
	}

	case Expression_Operator_NOT:
	case Expression_Operator_NEGATE:
	case Expression_Operator_LOG:
	{
		Exp* a = createExp(expression.children(0), source);
		Type t1 = a->getOutputElem().type;
		switch (expression.operator_()) {
		case Expression_Operator_NOT: TYPE_CHOICE_4(NotExp, !);
			assert(false);
		case Expression_Operator_NEGATE: TYPE_CHOICE_5(NegateExp, -);
			assert(false);
		case Expression_Operator_LOG: TYPE_CHOICE_6(LogExp, log);
			assert(false);
		default: assert(false);
		}
	}

	case Expression_Operator_IF:
	{
		Exp* a = createExp(expression.children(0), source);
		Exp* b = createExp(expression.children(1), source);
		Exp* c = createExp(expression.children(2), source);
		Type t1 = a->getOutputElem().type;
		Type t2 = b->getOutputElem().type;
		Type t3 = c->getOutputElem().type;
		assert(t1 == BOOL && t2 == t3);
		switch (t2) {
		case INT:
			return new IfExpr<int32_t > (a, b, c);
		case DOUBLE:
			return new IfExpr<double> (a, b, c);
		case BOOL:
			return new IfExpr<bool> (a, b, c);
		default: assert(false);
		}
	}

	default:
		//printf("Nie zdefiniowane wyrazenie\n") ;
		assert(false);
	}
}
